package com.yonyou.brigade.util;

import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.*;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.*;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.UnsupportedEncodingException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * httpclient封装类，被请求的方法需要是幂等的
 *
 * @since 2018-08-15
 */
public class HttpClientUtil {
    private static final Logger logger = LoggerFactory.getLogger(HttpClientUtil.class);

    private static final int CONNECT_TIMEOUT = 5000;//设置超时毫秒数
    private static final int SOCKET_TIMEOUT = 10000;//设置传输毫秒数
    private static final int REQUESTCONNECT_TIMEOUT = 3000;//获取请求超时毫秒数
    private static final int CONNECT_TOTAL = 200;//最大连接数
    private static final int CONNECT_ROUTE = 20;//设置每个路由的基础连接数
    private static final int VALIDATE_TIME = 30000;//设置重用连接时间
    private static final String RESPONSE_CONTENT = "通信失败";
    private static PoolingHttpClientConnectionManager manager = null;
    private static CloseableHttpClient client = null;

    static {
        // 设置client参数并实例化，且加入重试
        ConnectionSocketFactory csf = PlainConnectionSocketFactory.getSocketFactory();
        LayeredConnectionSocketFactory lsf = createSSLConnSocketFactory();
        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create().register("http", csf).register("https", lsf).build();
        manager = new PoolingHttpClientConnectionManager(registry);
        manager.setMaxTotal(CONNECT_TOTAL);
        manager.setDefaultMaxPerRoute(CONNECT_ROUTE);
        manager.setValidateAfterInactivity(VALIDATE_TIME);
        SocketConfig config = SocketConfig.custom().setSoTimeout(SOCKET_TIMEOUT).build();
        manager.setDefaultSocketConfig(config);
        RequestConfig requestConf = RequestConfig.custom().setConnectTimeout(CONNECT_TIMEOUT).setConnectionRequestTimeout(REQUESTCONNECT_TIMEOUT).setSocketTimeout(SOCKET_TIMEOUT).build();
        client = HttpClients.custom().setConnectionManager(manager).setDefaultRequestConfig(requestConf).setRetryHandler(
                //实现了HttpRequestRetryHandler接口的
                //public boolean retryRequest(IOException exception, int executionCount, HttpContext context)方法
                (exception, executionCount, context) -> {
                    if (executionCount >= 3)
                        return false;
                    //如果服务器断掉了连接那么重试
                    if (exception instanceof NoHttpResponseException)
                        return true;
                    //不重试握手异常
                    if (exception instanceof SSLHandshakeException)
                        return false;
                    //IO传输中断重试
                    if (exception instanceof InterruptedIOException)
                        return true;
                    //未知服务器
                    if (exception instanceof UnknownHostException)
                        return false;
                    //超时就重试
                    if (exception instanceof ConnectTimeoutException)
                        return true;
                    if (exception instanceof SSLException)
                        return false;
                    //幂等请求类型才重试
                    HttpClientContext cliContext = HttpClientContext.adapt(context);
                    HttpRequest request = cliContext.getRequest();
                    if (!(request instanceof HttpEntityEnclosingRequest))
                        return true;
                    return false;
                }).build();
        if (manager != null && manager.getTotalStats() != null)
            logger.info("客户池状态：" + manager.getTotalStats().toString());
    }

    // https避免需要证书，绕过验证
    private static SSLConnectionSocketFactory createSSLConnSocketFactory() {
        // 创建TrustManager
        X509TrustManager xtm = new X509TrustManager() {
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }
        };
        SSLContext ctx = null;
        try {
            // TLS1.0与SSL3.0基本上没有太大的差别，可粗略理解为TLS是SSL的继承者，但它们使用的是相同的SSLContext
            ctx = SSLContext.getInstance("TLS");
            // 使用TrustManager来初始化该上下文，TrustManager只是被SSL的Socket所使用
            ctx.init(null, new TrustManager[]{xtm}, null);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        }
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(ctx);
        return sslsf;
    }

    //创建httpClient上下文、发送请求并解析结果
    private static String res(HttpRequestBase method) {
        HttpClientContext context = HttpClientContext.create();
        CloseableHttpResponse response = null;
        String content = RESPONSE_CONTENT;
        try {
            response = client.execute(method, context);//执行GET/POST请求
            HttpEntity entity = response.getEntity();//获取响应实体
            if (entity != null) {
                Charset charset = ContentType.getOrDefault(entity).getCharset();
                content = EntityUtils.toString(entity, charset);
                EntityUtils.consume(entity);
            }
        } catch (ConnectTimeoutException cte) {
            logger.error("请求连接超时，由于 " + cte.getLocalizedMessage());
            cte.printStackTrace();
        } catch (SocketTimeoutException ste) {
            logger.error("请求通信超时，由于 " + ste.getLocalizedMessage());
            ste.printStackTrace();
        } catch (ClientProtocolException cpe) {
            logger.error("协议错误（比如构造HttpGet对象时传入协议不对(将'http'写成'htp')or响应内容不符合），由于 " + cpe.getLocalizedMessage());
            cpe.printStackTrace();
        } catch (IOException ie) {
            logger.error("实体转换异常或者网络异常， 由于 " + ie.getLocalizedMessage());
            ie.printStackTrace();
        } finally {
            if (response != null) {
                try {
                    response.close();
                } catch (IOException e) {
                    logger.error("响应关闭异常， 由于 " + e.getLocalizedMessage());
                }
            }
            if (method != null) {
                method.releaseConnection();
            }
        }
        return content;
    }

    public static String doGet(String url) {
        return get(url, null);
    }

    /**
     * post方式发送json数据的请求
     *
     * @param url    请求地址
     * @param cookie http请求的cookie
     * @return String 请求结果response中的entity
     **/
    public static String get(String url, String cookie) {
        HttpGet get = new HttpGet(url);
        if (StringUtils.isNotBlank(cookie)) {
            get.addHeader("cookie", cookie);
        }
        return res(get);
    }

    public static String doPostJsonForm(String url, String jsonString) {
        return postByUrlencoded(url, null, null, jsonString);
    }


    public static String doPostJson(String url, String jsonString) {
        return post(url, null, null, jsonString);
    }

    /**
     * post方式发送json数据的请求
     *
     * @param url        请求地址
     * @param headers    http请求的header
     * @param cookie     http请求的cookie
     * @param jsonString 请求的表单参数
     * @return String 请求结果response中的entity
     **/
    public static String post(String url, Map<String, String> headers, String cookie, String jsonString) {
        HttpPost post = new HttpPost(url);
        if (MapUtils.isNotEmpty(headers)) {
            for (Entry<String, String> entry : headers.entrySet()) {
                post.addHeader(entry.getKey(), entry.getValue());
            }
        }
        if (StringUtils.isNotEmpty(cookie)) {
            post.addHeader("cookie", cookie);
        }
        if (jsonString != null) {
            post.setEntity(new StringEntity(jsonString, ContentType.APPLICATION_JSON));
        }
        return res(post);
    }


    /**
     * post方式发送json数据的请求
     *
     * @param url        请求地址
     * @param headers    http请求的header
     * @param cookie     http请求的cookie
     * @param jsonString 请求的表单参数
     * @return String 请求结果response中的entity
     **/
    public static String postByUrlencoded(String url, Map<String, String> headers, String cookie, String jsonString) {
        HttpPost post = new HttpPost(url);
        if (MapUtils.isNotEmpty(headers)) {
            for (Entry<String, String> entry : headers.entrySet()) {
                post.addHeader(entry.getKey(), entry.getValue());
            }
        }
        if (StringUtils.isNotEmpty(cookie)) {
            post.addHeader("cookie", cookie);
        }
        if (jsonString != null) {
            StringEntity entity = new StringEntity(jsonString,"utf-8");
            entity.setContentType("application/x-www-form-urlencoded");
            post.setEntity(entity);

        }
        return res(post);
    }


    /**
     * post方式发送contentType为x-www-form-urlencoded的请求
     *
     * @param url     请求地址
     * @param headers http请求的header
     * @param cookie  http请求的cookie
     * @param param   请求的表单参数
     * @return String 请求结果response中的entity
     **/
    public static String postForm(String url, Map<String, String> headers, String cookie, Map<String, String> param) {
        HttpPost post = new HttpPost(url);
        if (MapUtils.isNotEmpty(headers)) {
            for (Entry<String, String> entry : headers.entrySet()) {
                post.addHeader(entry.getKey(), entry.getValue());
            }
        }
        if (StringUtils.isNotEmpty(cookie)) {
            post.addHeader("cookie", cookie);
        }
        if (param != null) {
            List<NameValuePair> nvps = new ArrayList<NameValuePair>();
            for (Entry<String, String> entry : param.entrySet()) {
                nvps.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
            }
            try {
                post.setEntity(new UrlEncodedFormEntity(nvps, "utf8"));
            } catch (UnsupportedEncodingException e) {
                logger.info("URL参数错误！");
            }
        }
        return res(post);
    }
}
